package pacman;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.lang.Math;

public class Agent {
	
	static int TREE_HEIGHT = 7;
	static final int ORIGINAL_HEIGHT = 7;
	static final int MAX_HEIGHT = 15;
	static final int STUCK_MAX = 3;
	static final int REMEMBER_LOC = 1;
	
	GameModel m_gameModel;
	static final int m_gameSizeX = 28;					// x size of the game
	static final int m_gameSizeY = 31;					// y size of the game
	int[][] game  = new int[m_gameSizeX][m_gameSizeY];	// grid to represent the game

	boolean useAI = true;				// random boolean?
	Point locNow = new Point(-1,-1);	// use to see if pacman moved
	Point locPrev1 = new Point (-1,-1);	// use to see if pacman moved
	Point locPrev2 = new Point (-1,-1);
	Point locTarget = new Point (-1,-1);
	List<Point> prevLoc = new LinkedList<Point>();
	
	Point recFrom, recTo;				// use for record keeping
	int dirPrev, dirCurr;				// use for record keeping
	
	// Variables for finding the next direction to go for random movement
	//Point[] pt = new Point[4];			//
	int[] d1 = new int [4]; 			//original
	int[] d2 = new int [4]; 			//dupe
	int[] d3 = new int [4]; 			//diff
	
	boolean build = false;				// build only one iteration (temp variable)
	boolean goRandom = false;

	// This direction list can direct where you want the pacman to go. Set pathListReset true for it to follow. (NOT WORKING (NOT TESTED))
	List<Direction> currentPath = new LinkedList<Direction>();
	List<Direction> newPath = new LinkedList<Direction>();
	boolean pathListReset = false;
	int pathIndex = 0;
	int pathValue = 0;
	boolean firstPath = true;
	boolean keepPath = true;
	
	int stuckIterations = 0;
	
	Agent (GameModel gameModel)
	{
		m_gameModel = gameModel;
		for (int i = 0; i < REMEMBER_LOC; i++) {
			prevLoc.add(new Point(-1,-1));
		}
	}
	
	/**
	 * Store into d1 the direction it can move. [up, down, left right]
	 * @return true if it can move; false if it cant move.
	 */
	public boolean getDirection(){
		Point[] pt = new Point[4];
		if(m_gameModel.m_things[0].canMove()){
			for(Direction i : Direction.values()){
				pt[i.getValue()-1] = new Point(-1,-1);
				//pt[i.getValue()-1] = new Point(m_gameModel.m_things[0].m_locX,m_gameModel.m_things[0].m_locY);
				m_gameModel.m_things[0].getDestination(i.getValue(), m_gameModel.m_things[0].m_locX, m_gameModel.m_things[0].m_locY, pt[i.getValue()-1]);
				if(pt[i.getValue()-1].x == m_gameModel.m_things[0].m_locX && pt[i.getValue()-1].y == m_gameModel.m_things[0].m_locY){
					d1[i.getValue()-1] = -1;
				}else{
					d1[i.getValue()-1] = 1;
				}
			}
		}else{
			//TODO: need to deal with what happen if it stuck. might be fix with ambigous 
			return false;
		}
		return true;
	}

	/**
	 * Check if new direction/path opens up besides going back, and store directions into d3.
	 * @return true if new direction; false otherwise.
	 */
	public boolean checkDirection(){
		if(d1.equals(d2)){
			return false;
		}else{
			int temp = 0;
			for(int i=0; i<4; i++){
				if(d1[i] != d2[i] && d1[i] != -1) d3[i]=1;
				else d3[i]=-1;
				if(m_gameModel.m_player.m_requestedDirection==1) d3[2-1]=-1;
				if(m_gameModel.m_player.m_requestedDirection==2) d3[1-1]=-1;
				if(m_gameModel.m_player.m_requestedDirection==3) d3[4-1]=-1;
				if(m_gameModel.m_player.m_requestedDirection==4) d3[3-1]=-1;
				temp += d3[i];
				d2[i] = d1[i];
			}
			if(temp==-4) return false;
		}
		return true;
	}
	
	/**
	 * Set the direction for agent to move.
	 * @param r is the direction. 0 up; 1 down; 2 left; 3 right;
	 */
	public void setDirection(int r){
		switch (r)
		{
		case 0:
			//System.out.println("UP");
			m_gameModel.m_player.m_requestedDirection = Thing.UP;
			break;
		case 1:
			//System.out.println("DOWN");
			m_gameModel.m_player.m_requestedDirection = Thing.DOWN;
			break;
		case 2:
			//System.out.println("LEFT");
			m_gameModel.m_player.m_requestedDirection = Thing.LEFT;
			break;
		case 3:
			//System.out.println("RIGHT");
			m_gameModel.m_player.m_requestedDirection = Thing.RIGHT;
			break;
		default:
			//System.out.println("NO DIRECTIONS?!");
		}
	}
	
	public void agentRandom(){
		//if(!useAI) return;
		if(getDirection() && checkDirection()){
			int r = (int)(Math.random()*4);
			while(d3[r]==-1){
				r = (int)(Math.random()*4);
			}
			switch (r) {
				case 0: System.out.print("randUP(1) "); break;
				case 1: System.out.print("randDOWN(2) "); break;
				case 2: System.out.print("randLEFT(3) "); break;
				case 3: System.out.print("randRIGHT(4) "); break;
			}
			setDirection(r);
		}
	}

	public void runner(){
		//posRec();
		if(!build){
			//build = true;	// comment to build only once/first iteration (for error checking only)
			buildResources();
		}
		if(!useAI) return;
	
		if (goRandom) {
			goRandom = false;
			agentRandom();
			return;
		}
		
		System.out.println("At index: "+pathIndex+"  Path size: "+currentPath.size());
		if(pathIndex < currentPath.size()){
			if(getDirection()){// && checkDirection()){
				printPossibleDir();
				
				System.out.println("Want to go: "+currentPath.get(pathIndex)+"("+(currentPath.get(pathIndex)).getValue()+")");
				if(d1[currentPath.get(pathIndex).getValue()-1]==1){
					System.out.println("Setting direction: "+(currentPath.get(pathIndex).getValue()));
					setDirection(currentPath.get(pathIndex).getValue()-1);
					if(posCheck(currentPath.get(pathIndex))) pathIndex++;
				}else{
					System.out.println("Direction not available yet");
					//stuckIterations++;
					if(currentPath.get(pathIndex).getValue() == m_gameModel.m_things[0].m_direction){
						System.out.println("PATH MISTAKE. USELESS DIRECTION -- ALREADY GOING");
						stuckIterations++;
						if(pathIndex<currentPath.size()){
							pathIndex++;
						}else{
							System.out.println("PATH MISTAKE. OUT OF BOUND -- no idea what to do");
						}
					}
				}
			}else{
				System.out.println("NO PATH YET");
				return;
			}
		}else{
			System.out.println("PATH DONE SWITCH TO RANDOM");
			agentRandom();
		}
		posRec();
	}
	
	private void printPossibleDir() {
		String output = "";
		output += "Possible dir: ";
		for(int i=0; i<d1.length; i++){
			if(d1[i]!=-1){
				switch (i) {
				case 0: output += "UP(1) "; break;
				case 1: output += "DOWN(2) "; break;
				case 2: output += "LEFT(3) "; break;
				case 3: output += "RIGHT(4) "; break;
				}
			}
		}
		System.out.println(output);
	}

	public void buildResources(){
		// tree need initial location/point
		Point currLoc = new Point(m_gameModel.m_things[0].m_locX,m_gameModel.m_things[0].m_locY);
		// create tree by setting initial node
		InfoTree tree = new InfoTree(m_gameModel, currLoc);
		// build the tree (doesnt auto build); null is starting point
		System.out.println(TREE_HEIGHT+" "+currLoc.x+" "+currLoc.y);
		tree.build(TREE_HEIGHT, null);
		// calculate the tree values
		tree.calculateTree();
		
		//while (tree.getTreeValue() <= (0) || TREE_HEIGHT == MAX_HEIGHT) {
		while (tree.getTreeValue() <= (0) && TREE_HEIGHT <= MAX_HEIGHT) {
			TREE_HEIGHT++;
			tree.build(TREE_HEIGHT, null);
			tree.calculateTree();
			System.out.print(".");
		}
		

		// print the tree is nice looking form
		//tree.printTree();
		// print the tree value in a nice looking form
		tree.printTreeNodeValues();

		
		if (tree.getTreeValue() < 0 && TREE_HEIGHT == MAX_HEIGHT) {
		//if (tree.getTreeValue() == 0) {
			goRandom = true;
			TREE_HEIGHT = ORIGINAL_HEIGHT;
			System.out.println("------------------");
			System.out.println("Going random");
			return;
		}else{
			System.out.println("------------------");
		}
		
		if (firstPath) {
			currentPath = tree.getDesiredPath();
			pathValue = tree.getTreeValue();
			firstPath = false;
			pathIndex = 0;
			System.out.println("First path");
			System.out.print("Full Path:");
			printPath(tree.fullPath);
		} else {
			newPath = tree.getDesiredPath();
			if(pathIndex >= currentPath.size() || stuckIterations > STUCK_MAX){// || !newPath.get(0).equals(currentPath.get(pathIndex))){
				System.out.println("New path - (pathIndex "+pathIndex+" >= "+currentPath.size()+")");
				TREE_HEIGHT = ORIGINAL_HEIGHT;
				if(newPath.size() == 0){
					System.out.println("omg no path?!?");
					if (pathIndex > 0) pathIndex--;
				}else{
					currentPath = newPath;
					pathIndex = 0;
				}
				stuckIterations = 0;
				System.out.print("Full Path:");
				printPath(tree.fullPath);
			}else{
				System.out.println("Same path");
				//stuckIterations++;
			}
		}
		System.out.print("Final Path:");
		printPath(currentPath);
	}
	
	void printPath(List<Direction> currentPath) {
		//System.out.println();
		String output = "[";
		for (Direction dir : currentPath) {
			/*
			switch (dir) {
				case UP: output += "UP "; break;
				case DOWN: output += "DOWN "; break;
				case LEFT: output += "LEFT "; break;
				case RIGHT: output += "RIGHT "; break;
			}
			*/
			output +=dir+" ";
		}
		output += "]";
		System.out.println(output);
	}
	
	/**
	 * Simple method to see if position of pacman changed since it was last called.
	 * @return
	 */
	boolean pcheck = false;
	boolean posCheck(Direction r){
		if(!pcheck){
			setTarget(r);
			pcheck = true;
		}
		locNow = new Point(m_gameModel.m_things[0].m_locX, m_gameModel.m_things[0].m_locY);

		//if(locTarget==null) setTarget(r);
		if(!locNow.equals(locTarget)){
			System.out.println("pacman stay -- from ["+locNow.x+" "+locNow.y+"] to ["+locTarget.x+" "+locTarget.y+"] dir "+r);
			return false;
		}else{
			System.out.println("pacman increment -- from ["+locNow.x+" "+locNow.y+"] to ["+locTarget.x+" "+locTarget.y+"] dir "+r);
			//locTarget = null;
			pcheck = false;
		}
		
		/*if(locNow.equals(locPrev1) || locNow.equals(locPrev2)){
			return false;
		}else{
			System.out.println("pacman moved -- incrementing");
			locPrev2 = locPrev1;
			locPrev1 = new Point(locNow.x, locNow.y);
			//return true;
		}*/
		/*
		for (Point p : prevLoc) {
			if (locNow.equals(p))  {
				return false;
			}
		}
		System.out.println("pacman moved -- incrementing");
		prevLoc.remove(0);
		prevLoc.add(locNow);
		*/
		return true;
	}
	
	private void setTarget(Direction r) {
		System.out.println("Set traget");
		switch (r) {
			case UP:	locTarget = new Point(m_gameModel.m_things[0].m_locX, m_gameModel.m_things[0].m_locY-1); break;
			case DOWN: 	locTarget = new Point(m_gameModel.m_things[0].m_locX, m_gameModel.m_things[0].m_locY+1); break;
			case LEFT: 	locTarget = new Point(m_gameModel.m_things[0].m_locX-1, m_gameModel.m_things[0].m_locY); break;
			case RIGHT: locTarget = new Point(m_gameModel.m_things[0].m_locX+1, m_gameModel.m_things[0].m_locY); break;
		}
	}

	boolean recCheck = false;
	void posRec(){
		if(game == null){
			game = new int[m_gameSizeX][m_gameSizeY];
			for(int i=0; i<m_gameSizeY; i++){
				for(int j=0; j<m_gameSizeX; j++){
					game[j][i] = 0;
				}
			}
		}
		
		if(recFrom == null){
			System.out.println("Starting.. recFrom null");
			game[m_gameModel.m_things[0].m_locX][m_gameModel.m_things[0].m_locY]++;
		}
		recFrom = new Point(m_gameModel.m_things[0].m_locX, m_gameModel.m_things[0].m_locY);
		
		if(dirCurr == 0 || dirCurr!=dirPrev){
			System.out.println("Direction changed");
			dirPrev = m_gameModel.m_things[0].m_direction;
			recCheck = false;
		}
		dirCurr = m_gameModel.m_things[0].m_direction;

		if(!recCheck){
			switch (dirCurr) {
				case 1:	recTo = new Point(m_gameModel.m_things[0].m_locX, m_gameModel.m_things[0].m_locY-1); break;
				case 2: recTo = new Point(m_gameModel.m_things[0].m_locX, m_gameModel.m_things[0].m_locY+1); break;
				case 3: recTo = new Point(m_gameModel.m_things[0].m_locX-1, m_gameModel.m_things[0].m_locY); break;
				case 4: recTo = new Point(m_gameModel.m_things[0].m_locX+1, m_gameModel.m_things[0].m_locY); break;
				default: System.out.println("RECORDING ERROR");
			}
			recCheck = true;
		}
		
		if(!recFrom.equals(recTo)){
			//System.out.println("pacman stay -- from ["+locNow.x+" "+locNow.y+"] to ["+locTarget.x+" "+locTarget.y+"] dir "+d);
			return;
		}else{
			//System.out.println("pacman increment -- from ["+locNow.x+" "+locNow.y+"] to ["+locTarget.x+" "+locTarget.y+"] dir "+d);
			game[m_gameModel.m_things[0].m_locX][m_gameModel.m_things[0].m_locY]++;
			//game[recTo.x][recTo.y]++;
			recCheck = false;
			//printGame();
		}
	}
	/**
	 * This will bring the game out in a grid showing the tree
	 */
	public void printGame() {
		System.out.println();
		for(int i=0; i<m_gameSizeY; i++){
			for(int j=0; j<m_gameSizeX; j++){
				if(game[j][i]!=0){
					if(game[j][i]>9){
						System.out.print(game[j][i]);
					}else{
						System.out.print(game[j][i]+" ");
					}
				}else{
					System.out.print("  ");
				}
			}
			System.out.println();
		}
	}
}
